package fallowme.comm;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.LinkedBlockingQueue;

import fallowme.comm.ClientComm;
import fallowme.comm.ClientSideMessage;
import fallowme.comm.ServerSideMessage.Type;


/**
 * Communication class for {@link birdsong.client.BirdsongClient Birdsong clients}. Birdsong clients must 
 * communicate with a Birdsong server exclusively through this class. This class implements the ClientComm
 * interface. 
 * 
 * @author Bruno Parra, Leo Dias, Miguel Lemos, Ruben Gomes
 * @version 1.0
 */
public class BirdSongClientComm implements ClientComm {

	private Socket socket;
	private ObjectOutputStream out;
	private String nickname;
	private LinkedBlockingQueue<BirdSongClientSideMessage>  incomingMessages;

	/**
	 * Establish a connection to a Birdsong server. 
	 * 
	 * @param host                    Address of the server. For instance "localhost", "192.168.1.1", and so on.
	 * @param nickname                The nickname to use for the client.
	 * @throws UnknownHostException   Thrown if the host cannot be found.
	 * @throws IOException            Thrown if a connection cannot be established.
	 */
	@Override
	public void connect(String host, String nickname) throws UnknownHostException, IOException {
		assert host != null && !host.isEmpty();
		assert nickname != null && !nickname.isEmpty();
		
		this.nickname = nickname;
		socket = new Socket(host, BirdSongServerComm.SERVER_PORT);
		out = new ObjectOutputStream(socket.getOutputStream());
		incomingMessages = new LinkedBlockingQueue<BirdSongClientSideMessage>();

		//sends to the server a message that a client has connected.
		out.writeObject(new BirdSongServerSideMessage(Type.CLIENT_CONNECTED, 
				null, nickname));

		//starts a DealWithServer thread to handle incoming input's.
		new DealWithServer(socket, incomingMessages).start();
	}

	/**
	 * Check if currently connected to a host.
	 * 
	 * @return <b>true</b> if currently connected to a host, <b>false</b> otherwise.
	 */
	@Override
	public boolean isConnected() {
		return !socket.isClosed();
	}

	/**
	 * Disconnects a Client from a Birdsong server.
	 * A message waring that the client will disconnect to the server is sent 
	 * before closing the socket.
	 * Pending messages are not guaranteed to be delivered before disconnecting.
	 */	
	@Override
	public void disconnect() {
		try {
			//sends to the server a message that a client will disconnect.
			out.writeObject(new BirdSongServerSideMessage(
					Type.CLIENT_DISCONNECTED, null, nickname));
			//closes the connection.
			socket.close();
		} catch (IOException e) {
			System.out.println("Failed to disconnect.");
		}
	}

	/**
	 * Get the next message received from the server. If no message 
	 * is has been received, the method blocks until a message has 
	 * been received. If no message will ever be received (connection is down), null is returned.
	 * 
	 * @return The next message sent by the server to the client or null if no message will ever be received (disconnected from server).
	 */
	@Override
	public ClientSideMessage getNextMessage() {
		BirdSongClientSideMessage msg = null;

		//If no message will ever be received (connection is down), null is returned.
		if(isConnected()){
			try {
				msg = incomingMessages.take();
			} catch (InterruptedException e) {
				System.out.println("Failed to get next message.");
			}
		}
		return msg;
	}

	/** 
	 * Check if a message from the server is pending. If {@link #hasNextMessage()} returns <b>true</b>, a call to {@link #getNextMessage()} 
	 * will return immediately with the oldest message in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call to {@link #getNextMessage()} 
	 * will block until a message has been received.
	 * 
	 * @return <b>true</b> if a message from the server is currently pending, otherwise <b>false</b>.
	 */
	@Override
	public boolean hasNextMessage() {
		return incomingMessages.peek() != null;
	}

	/**
	 * Send a Birdsong message from this client to the server which should then relay the message to all followers.
	 * 
	 * @param birdsongMessage The Birdsong message to be sent.
	 */
	@Override
	public void sendBirdsongMessage(String birdsongMessage) {
		try {
			out.writeObject(new BirdSongServerSideMessage(
					Type.BIRDSONG_MESSAGE, birdsongMessage, nickname));
		} catch (IOException e) {
			System.out.println("Failed to send message.");
		}

	}

	/**
	 * Request to follow another client.
	 * 
	 * @param nickname the nickname of the client that should be followed.
	 */
	@Override
	public void follow(String nickname) {
		try {
			out.writeObject(new BirdSongServerSideMessage(
					Type.FOLLOW, nickname, this.nickname));
		} catch (IOException e) {
			System.out.println("Failed to follow " + nickname + ".");
		}
	}

	/**
	 * Request to unfollow a client.
	 * 
	 * @param nickname the nickname of the client currently followed that should be unfollowed.
	 */
	@Override
	public void unfollow(String nickname) {
		try {
			out.writeObject(new BirdSongServerSideMessage(
					Type.UNFOLLOW, nickname, this.nickname));
		} catch (IOException e) {
			System.out.println("Failed to unfollow " + nickname + ".");
		}
	}
}
